#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <ctype.h>

#define NUM 1024
#define SIZE 64
//#define Debug 1

#define NoneRedir 0
#define OutputRedir 1
#define AppendRedir 2
#define InputRedir 3

int redir = NoneRedir;
char* filename = NULL;
char cwd[1024];// 不能存临时变量，因为环境变量中存的是字符串地址
char env_val[1024];
int lastcode = 0;//最后的退出码

char* homePath()
{
    char* home = getenv("HOME");
    if(home)
        return home;
    else 
        return ".";
}

const char* getUsername()
{
    const char* name = getenv("USER");
    if(name)
        return name;
    else 
        return "none";
}
const char* getHostname()
{
    const char* name = getenv("HOSTNAME");
    if(name)
        return name;
    else 
        return "none";
}
const char* getCwd()
{
    const char* name = getenv("PWD");
    if(name)
        return name;
    else 
        return "none";
}

int getUserCommand(char* command,int num)
{
    printf("[%s&%s %s]# ",getUsername(),getHostname(),getCwd());
    char* r = fgets(command,num,stdin);//最终还会读入\n
    if(r==NULL)
        return -1; //读取失败
    //去掉最后\n
    command[strlen(command)-1] = '\0';//不会越界 因为至少有'\n'

    return strlen(command);
}

void commandSplit(char* in,char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in," ");
    
    while(out[argc++] = strtok(NULL," "));//继续截取原字符串传入NULL,原字符串被修改
#ifdef Debug 
    for(int i = 0;out[i];i++)
    {
        printf("%d->%s\n",i,out[i]);
    }
#endif
}

void cd(const char* path)
{
    chdir(path);//谁执行谁的工作目录改变
    char temp[1024];
    getcwd(temp,sizeof(temp));//重新获取当前工作目录


    sprintf(cwd,"PWD=%s",temp);//格式化存入cwd
    putenv(cwd);//存在PWD,会覆盖，不会新增
}
//内建命令就是bash自己执行的，类似于自己内部的一个函数，不用子进程执行
//1->yes  0->no
int doBuildin(char* argv[])
{
   if(strcmp(argv[0],"cd")== 0)
   {
       //执行cd命令
       char* path = NULL;
       if(argv[1] == NULL)
           path = homePath();
       else 
           path = argv[1];
       cd(path);   
       return 1;
   }
   else if(strcmp(argv[0],"export")==0)
   {
       if(argv[1]==NULL)return 1;
       //给自己进程加环境变量
       strcpy(env_val,argv[1]);
       putenv(env_val);//放入的是地址
       return 1;
   }
   else if(strcmp(argv[0],"echo")==0)
   {
        if(argv[1]==NULL)
        {
            printf("\n");
            return 1;
        }
        
        if(*(argv[1])=='$' && strlen(argv[1])>1)
        {
             char*val = argv[1]+1;//$PATH  argv[1]是$的地址，+1指向P  $?
             if(strcmp(val,"?")==0)
             {
                 printf("%d\n",lastcode);
                 lastcode = 0;
             }
             else 
             {
                 const char* ev = getenv(val);
                 if(ev)
                     printf("%s\n",ev);
                 else 
                     printf("\n");
             }
             return 1;
        }
        else 
        {
            printf("%s\n",argv[1]);
            return 1;
        }

   }

    
   return 0;
}
int execute(char* argv[])
{
    pid_t id = fork();
    if(id<0)
    {
        return 1;
    }
    else if(id==0)
    {
        //child
        
        //程序替换， 不会影响曾经的重定向。  因为程序替换不改变进程，文件是由进程打开的，不影响内核数据结构， 只是替换内存中代码和数据
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename,O_RDONLY);
            dup2(fd,0);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename,O_CREAT|O_WRONLY|O_APPEND,0666);
            dup2(fd,1);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC ,0666);
            dup2(fd,1);
            
        }

        execvp(argv[0],argv);//子进程挂掉不影响父进程

        //cd .. 只是让子进程执行，只是改变子进程的工作目录，执行完就退出，父进程没有执行，
        //有一批命令，不能让子进程执行，必须让父进程执行--内建命令

        //替换失败
        exit(1);
    }    
    else 
    {
        //father 等待子进程
        int status = 0;
        pid_t rid = waitpid(-1,&status,0);
    
        if(rid>0){
            lastcode = WEXITSTATUS(status);
        }//wait success 
    
    }
    return 0;
}

#define Skipname(pos) do {while(isspace(*pos)) pos++;}while(0)

void cheakRedir(char command[],int len)
{
    char* end = command + len - 1;
    char* begin = command;
    while(end>=begin)
    {
        if(*end == '>')
        {
            if(*(end-1) == '>')
            {
                *(end-1) = '\0';
                redir = AppendRedir; 
                filename = end+1;
                Skipname(filename);
                break;
            }
            else 
            {
                *end = '\0';
                redir = OutputRedir; 
                filename = end+1;
                Skipname(filename);
                break;
            }   

        }
        else if(*end == '<')
        {
            *end = '\0';
            redir = InputRedir;
            filename = end + 1;
            Skipname(filename);//如果有空格，就跳过
            break;
        }   
        else 
        {
            end--;
        }
    }
}


int main()
{
    while(1)
    {
        redir = NoneRedir;
        filename = NULL;

        char usercommand[NUM];
        char* argv[SIZE] = {NULL};
        
        //1.打印提示符&&获取用户命令字符串
        int ret = getUserCommand(usercommand,NUM);
        if(ret <= 0)
        {
            continue;
        }
        //1.1 检查是否有重定向 
        cheakRedir(usercommand,strlen(usercommand));
    
        //2.分割字符串
        commandSplit(usercommand,argv);
        //3.检查内建命令,并执行 cheak buildin command 
        ret = doBuildin(argv);
        if(ret) continue;
        //3.执行对应命令
        execute(argv);
    
    }    
    return 0;
}
